#include "dominion.h"
#include "dominion_helpers.h"
#include "rngs.h"
#include "testutils.h"
#include "interface.h"
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/time.h>
#include <time.h>
#include <assert.h>
#include <getopt.h>
#include <string.h>
#include <stdarg.h>

// This is an integration test designed to combinatorially test every card
// combo within certain limitations. The individual cards being played aren't
// tested that thoroughly, that's the job of the unit tests.

#define MAX_COMBO_LENGTH 8

typedef struct
{
    int cardType, variant;
    int coinBonus, actionBonus, buyBonus, cardBonus;
} cardspec_t;

cardspec_t cards[] = 
{
    {council_room,0,    0, 0, 1, 4},
    {smithy,0,          0, 0, 0, 3},
    {village,0,         0, 2, 0, 1},
    {great_hall,0,      0, 1, 0, 1},
    {minion,0,          2, 1, 0, 0}, // when played with choice1 = 1
    {steward,0,         0, 0, 0, 2}, // choice1 = 1
    {steward,1,         2, 0, 0, 0}, // choice1 = 2
    {cutpurse,0,        2, 0, 0, 0},
    {embargo,0,         2, 0, 0, 0}, // choice1 always = 0 for curse
};
#define num_card_types (sizeof(cards)/sizeof(cardspec_t))

// Tree structure where each node is a predicted game state and each child
// represents the game state after the corresponding card is played.
typedef struct statenode_s
{
    int                 depth;
    int                 coinBonus, numActions, numBuys, numCards;
} statenode_t;

// Top-level node represents the beginning of a turn; the tree will get filled
// out by init_combotree.
static statenode_t combotree[1] =
{
    {
        0,
        0, 1, 1, 5
    }
};

static void test_combo (int comboLength, statenode_t **stateSequence, const int *cardSequence);

static void test_combotree (statenode_t *state)
{
    static statenode_t *stateSequence[MAX_COMBO_LENGTH];
    static int cardSequence[MAX_COMBO_LENGTH];
    
    // No possible moves left
    if (state->numActions == 0 || state->numCards == 0 || state->depth == MAX_COMBO_LENGTH)
    {
        test_combo (state->depth, stateSequence, cardSequence);
        return;
    }
    
    statenode_t *children = malloc (num_card_types * sizeof(statenode_t));
    memset (children, 0, num_card_types * sizeof(statenode_t));
    
    for (int i = 0; i < num_card_types; i++)
    {
        statenode_t child;
        
        child.coinBonus = state->coinBonus + cards[i].coinBonus;
        child.numActions = state->numActions + cards[i].actionBonus - 1;
        child.numCards = state->numCards + cards[i].cardBonus - 1;
        child.numBuys = state->numBuys + cards[i].buyBonus;
        child.depth = state->depth + 1;
        
        cardSequence[state->depth] = i;
        stateSequence[state->depth] = &child;
        test_combotree (&child);
    }
}

int numcombos = 0;
static void test_combo (int comboLength, statenode_t **stateSequence, const int *cardSequence)
{
    int handPos[MAX_COMBO_LENGTH];
    memset (handPos, -1, sizeof(handPos));
    needed_card_t nc[MAX_COMBO_LENGTH];
    struct gameState G;
    
    // This goes unused, the combo tester has its own consistency checking 
    // system. The one built into the testutils library is too slow for this
    // test, and more meticulous than we need for this test. setup_test_game
    // expects this to be here, though.
    expectedState_t expected; 
    
    pass_printf ("%s: TEST combo %d:", __func__, numcombos++);
    
    // Arrange them so that they can be played one after the other from the
    // end. (NOTE: if the algorithm to remove a card from the player's hand is
    // not specified and is implementation-specific, then this code won't be
    // good enough.)
    int slotNum, i;
    int numDrawn = 5, deckPadding = 0;
    for (slotNum = 4, i = 0; i < comboLength; i++, slotNum--)
    {
        char name[MAX_STRING_LENGTH];
        int card = cards[cardSequence[i]].cardType;
        
        cardNumToName (card, name);
        pass_printf (" %s", name);
        if (card == steward)
            pass_printf ("(%d)", cards[cardSequence[i]].variant);
        
        nc[i].cardtype = card;
        nc[i].position = slotNum;
        
        int addPadding = cards[cardSequence[i]].cardBonus + numDrawn - comboLength;
        if (addPadding < 0)
            addPadding = 0;
        deckPadding += addPadding;        
        for (int j = cards[cardSequence[i]].cardBonus - 1; j >= addPadding; j--)
        {
            if (numDrawn < comboLength)
                handPos[numDrawn++] = stateSequence[i]->numCards - j;
        }
        
        if (slotNum >= 0)
            handPos[i] = slotNum;
        else
            assert (deckPadding + numDrawn - i - 1 == stateSequence[i]->numCards);
        
        // Account for the card discard algorithm that will borrow a card from
        // the end to fill an empty slot
        for (int j = i + 1; j < numDrawn; j++)
        {
            if (handPos[j] == stateSequence[i]->numCards)
            {
                handPos[j] = handPos[i];
                break;
            }
        }
    }
    pass_printf ("\n");
    
    setup_test_game (comboLength, nc, &G, &expected);
    
    int player = G.whoseTurn;
    
    // We only want 5 cards in the current hand. Because of nc, up to 5 of the
    // first action cards in cardSequence are already in the hand. We may need
    // to keep some of the built-in estates and coppers in the hand as 
    // padding, but the rest should go in the discard pile. This is results in
    // a semi-accurate simulation of an actual hand that a player might be 
    // dealt and have to play from.
    while (G.handCount[player] > 5)
    {
        G.discard[player][G.discardCount[player]++] =
            G.hand[player][--G.handCount[player]];
        G.hand[player][G.handCount[player]] = -1;
    }
    
    // Make sure the player has a total number of cards big enough for all the
    // card bonuses to work by padding out the discard pile with coppers.
    for (i = 0; i < deckPadding; i++)
        gainCard (copper, &G, 0, player);
    
    // "Stack" the deck with the rest of the cards in the combo that wouldn't
    // fit in the current hand.
    for (i = comboLength - 1; i >= 5; i--)
        gainCard (cards[cardSequence[i]].cardType, &G, 1, player);
    
    for (i = 0; i < comboLength; i++)
    {
        cardspec_t *cardspec = &cards[cardSequence[i]];
        char name[MAX_STRING_LENGTH];
        int card = cardspec->cardType;
        int variant = cardspec->variant;
        
        cardNumToName (card, name);
        
        if (G.hand[player][handPos[i]] != card)
        {
            printf ("FAIL sanity checking card (%d != %d)\n", G.hand[player][handPos[i]], card);
            log_print ();
            log_flush ();
            assert (G.hand[player][handPos[i]] == card);
        }
        
        int choice1 = 0, choice2 = 0;
        
        if (card == minion && variant == 0)
            choice1 = 1;
        else if (card == steward && variant == 0)
            choice1 = 1;
        else if (card == steward && variant == 1)
            choice1 = 2;
        else if (card == embargo)
            choice1 = 0;
        
        int errcode = playCard (handPos[i], choice1, choice2, 0, &G);
    
        assert_notequal (errcode, -1, "playing the %s", name);
        assert_equal (  G.coinBonus, stateSequence[i]->coinBonus,
                        "verifying the coin bonus" );
        assert_equal (  G.numActions, stateSequence[i]->numActions,
                        "verifying the number of actions" );
        assert_equal (  G.numBuys, stateSequence[i]->numBuys,
                        "verifying the number of buys" );
        assert_equal (  G.handCount[player], stateSequence[i]->numCards,
                        "verifying the hand size" );
    }
    
    // Misc. sanity checking
    endTurn (&G);
    
    log_flush ();
}

int main (int argc, char *argv[])
{
    parse_args (argc, argv);
    
#if MAX_COMBO_LENGTH > 7
    printf ("Please allow some time, on my computer this takes 20+ seconds.\n");
#endif
    fflush (stdout);
    
    test_combotree (combotree);
    
    printf ("\n%d combos tested\n", numcombos);
}
